#include "Framework.h"

Framework::Framework(BaseApp* pGameApp) : m_pDI(NULL), m_active(true), m_renderingPaused(false), m_timerPaused(false), m_closing(false) {
	m_pGameApp = pGameApp;
	m_pGraphics = new Graphics();
	m_pTimer = new Timer();
}

void Framework::Release() {
	m_closing = true;
	SAFE_RELEASE(m_pGraphics);
	OnLostDevice();
	OnDestroyDevice();
}

bool Framework::Initialize(char* title, HINSTANCE hInstance, int width, int height) {
	m_hInstance = hInstance;
	m_windowWidth = width;
	m_windowHeight = height;

	// define the window
	WNDCLASSEX wcex;
	wcex.cbSize			= sizeof(WNDCLASSEX);
	wcex.style			= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
	wcex.lpfnWndProc	= (WNDPROC)Framework::StaticWndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDC_ICON));
	wcex.hCursor		= LoadCursorFromFile("Graphics\\GameUI\\GameCursor.cur");
	wcex.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= title;
	wcex.hIconSm		= LoadIcon(hInstance, MAKEINTRESOURCE(IDC_ICON));

	// register the window
	RegisterClassEx(&wcex);

	// create the window
	/*m_hWnd = CreateWindow(title, title, WS_CUSTOMWINDOW, CW_USEDEFAULT, 
						0, width, height, NULL, NULL, hInstance, this);*/
	m_hWnd = CreateWindowExA(0, title, title, WS_CUSTOMWINDOW, g_WindowX, 
							g_WindowY, width, height, NULL, NULL, hInstance, this);

	// adjust to desired size
	RECT rect = {0, 0, width, height};
	AdjustWindowRect(&rect, GetWindowLong(m_hWnd, GWL_STYLE), false);
	SetWindowPos(m_hWnd, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top, 
				SWP_NOZORDER | SWP_NOMOVE);

	ShowWindow(m_hWnd, SW_SHOW);
	UpdateWindow(m_hWnd);

	// save current location/size
	ZeroMemory(&m_wp, sizeof(WINDOWPLACEMENT));
	m_wp.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(m_hWnd, &m_wp);

	m_pGraphics->setAntiAlising(g_AAEnabled);

	// initialize Direct3D
	if(!m_pGraphics->Initialize(m_hWnd)) {
		SHOWERROR("Graphics->Initialize() - Failed", __FILE__, __LINE__);
		return false;
	}

	// initialize DirectInput
	if(FAILED(DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDI, NULL))) {
		SHOWERROR("DirectInput8() - Failed", __FILE__, __LINE__);
		return false;
	}
	if(!m_mouse.Initialize(m_pDI, m_hWnd, DIT_MOUSE, DISCL_FOREGROUND | DISCL_EXCLUSIVE)) {
		SHOWERROR("Mouse.Initialize() - Failed", __FILE__, __LINE__);
		return false;
	}
	if(!m_keyboard.Initialize(m_pDI, m_hWnd, DIT_KEYBOARD, DISCL_FOREGROUND | DISCL_EXCLUSIVE)) {
		SHOWERROR("Keyboard.Initialize() - Failed", __FILE__, __LINE__);
		return false;
	}

	OnCreateDevice();
	OnResetDevice();

	// start the timer
	Pause(false, false);

	return true;
}

void Framework::Run() {
	MSG msg;
	while(1) {
		// did we recieve a message, or are we idling?
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			if(msg.message == WM_QUIT)
				break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		} else {
			// advance game frame
			if(m_pGraphics->getDevice() != NULL && m_active) {
				OnUpdateFrame();
				OnRenderFrame();
			}
		}
	}
}

void Framework::OnCreateDevice() {
	if(m_pGameApp != NULL && m_pGraphics != NULL) {
		m_pGameApp->OnCreateDevice(m_pGraphics->getDevice());
	}
}

void Framework::OnResetDevice() {
	// start the timer
	Pause(false, false);
	if(m_pGameApp != NULL && m_pGraphics != NULL) {
		if(m_pGraphics->getAntiAlising() /*&& m_pGraphics->getVertexProcessing() != D3DCREATE_SOFTWARE_VERTEXPROCESSING*/) {
			m_pGameApp->setAA(true);
		}
		m_pGameApp->OnResetDevice(m_pGraphics->getDevice());
	}
}

void Framework::OnLostDevice() {
	// stop the timer only if we're not closing down
	// or else stack corruption on return from Pause(bool, bool)
	if(!m_closing)
		Pause(true, true);
	if(m_pGameApp != NULL)
		m_pGameApp->OnLostDevice();
}

void Framework::OnDestroyDevice() {
	m_mouse.Release();
	m_keyboard.Release();
	SAFE_RELEASE(m_pDI);
	if(m_pGameApp != NULL)
		m_pGameApp->OnDestroyDevice();
}

void Framework::OnUpdateFrame() {
	if(m_pTimer != NULL)
		m_pTimer->Update();

	if(m_pGameApp != NULL && m_pGraphics != NULL && m_pTimer != NULL) {
		float elapsedTime = m_pTimer->getElapsedTime();
		// send out input data
		m_mouse.Read();
		m_keyboard.Read();
		long xDelta = m_mouse.getChangeInX();
		long yDelta = m_mouse.getChangeInY();
		long zDelta = m_mouse.getChangeInZ();
		bool* pMouseButtons = m_mouse.getButtons();
		bool* pPressedKeys = m_keyboard.getKeys();
		m_pGameApp->ProcessInput(xDelta, yDelta, zDelta, pMouseButtons, pPressedKeys, elapsedTime, m_hWnd);
		if(pPressedKeys[DIK_1]) {
			m_mouse.Initialize(m_pDI, m_hWnd, DIT_MOUSE, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
			m_keyboard.Initialize(m_pDI, m_hWnd, DIT_KEYBOARD, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
		}
		if(pPressedKeys[DIK_2]) {
			m_mouse.Initialize(m_pDI, m_hWnd, DIT_MOUSE, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
			m_keyboard.Initialize(m_pDI, m_hWnd, DIT_KEYBOARD, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		}

		// send out OnUpdateFrame()
		m_pGameApp->OnUpdateFrame(m_pGraphics->getDevice(), elapsedTime);
	}
}

void Framework::OnRenderFrame() {
	if(!m_active || (m_pGraphics->getDevice() == NULL))
		return;

	CheckAndResetLostDevice();

	if(m_pGameApp != NULL && !m_renderingPaused && m_pTimer != NULL)
		m_pGameApp->OnRenderFrame(m_pGraphics->getDevice(), m_pTimer->getElapsedTime());
}

void Framework::Pause(bool rendering, bool timer) {
	m_renderingPauseCount += rendering ? 1 : -1;
	m_renderingPauseCount = (m_renderingPauseCount < 0) ? 0 : m_renderingPauseCount;

	m_timerPauseCount += timer ? 1 : -1;
	m_timerPauseCount = (m_timerPauseCount < 0) ? 0 : m_timerPauseCount;

	m_renderingPaused = (m_renderingPauseCount > 0);
	m_timerPaused = (m_timerPauseCount > 0);

	if(m_timerPaused && m_pTimer != NULL) {
		m_pTimer->Stop();
	} else if(!m_timerPaused && m_pTimer != NULL) {
		m_pTimer->Start();
	}
}

void Framework::CheckAndResetLostDevice() {
	// check for lost device
	HRESULT result = m_pGraphics->getDevice()->TestCooperativeLevel();
	if(FAILED(result)) {
		if(result == D3DERR_DEVICELOST) {
			Sleep(50);
			return;
		} else {
			OnLostDevice();
			if(m_pGraphics->Reset() == D3DERR_DEVICELOST) {
				// device is stil lost
				Sleep(50);
				return;
			} else {
				OnResetDevice();
			}
		}
	}
}

void Framework::LockKey(DWORD key) {
	m_keyboard.LockKey(key);
}

LRESULT CALLBACK Framework::StaticWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	if(msg == WM_CREATE) {
		SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG)((CREATESTRUCT *)lParam)->lpCreateParams);
	}

	Framework *targetApp = (Framework*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

	if(targetApp)
		return targetApp->WndProc(hWnd, msg, wParam, lParam);

	return DefWindowProc(hWnd, msg, wParam, lParam);
}

LRESULT CALLBACK Framework::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch(message) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		if(m_pGraphics->getDevice()) {
			OnUpdateFrame();
			OnRenderFrame();
		} 
		ValidateRect(hWnd, 0);
		return 0;
	case WM_SIZE:
		if(wParam == SIZE_MINIMIZED) {
			// disable application on minimized
			m_active = false;
		} else {
			m_active = true;
			m_windowWidth = LOWORD(lParam);
			m_windowHeight = HIWORD(lParam);
			if(m_pGraphics->getDevice()) {
				OnLostDevice();
				m_pGraphics->Reset(); 
				OnResetDevice();
				OnUpdateFrame();
				OnRenderFrame();
			}
		}
		return 0;
	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

long Framework::getXCursor() {
	return m_mouse.getX();
}

long Framework::getYCursor() {
	return m_mouse.getY();
}

int Framework::getWidth() {
	return m_windowWidth;
}

int Framework::getHeight() {
	return m_windowHeight;
}

float Framework::getFPS() {
	if(m_pTimer != NULL) {
		return m_pTimer->getFPS();
	}
	return 0.0f;
}
